package com.wuhao.code.check.model

import com.aegis.kotlin.containsAny
import com.aegis.mybatis.xmlless.AnnotatedElementDetailInfo
import com.aegis.mybatis.xmlless.ElementInfoBuilder
import com.aegis.mybatis.xmlless.annotations.JsonResult
import com.aegis.mybatis.xmlless.annotations.PropertyMapping
import com.aegis.mybatis.xmlless.annotations.XmllessMethod
import com.aegis.mybatis.xmlless.constant.ClassNames
import com.aegis.mybatis.xmlless.model.FieldMappingList
import com.aegis.mybatis.xmlless.model.XmllessTableInfo
import com.aegis.mybatis.xmlless.model.base.BaseXmllessMethodWrapper
import com.aegis.mybatis.xmlless.model.criteria.ParameterInfo
import com.aegis.mybatis.xmlless.resolver.MappingResolver
import com.aegis.mybatis.xmlless.util.TableInfoProvider
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiField
import com.intellij.psi.PsiParameter
import com.wuhao.code.check.*
import com.wuhao.code.check.model.base.BasePsiMethodWrapper
import org.apache.ibatis.annotations.Param
import org.jetbrains.kotlin.idea.intentions.typeArguments
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.getReturnTypeReference
import org.jetbrains.kotlin.nj2k.postProcessing.resolve
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.containingClass

fun resolveKtClass(value: KtExpression?): KtClass? {
  if (value is KtClassLiteralExpression) {
    val exp = value.receiverExpression
    if (exp is KtNameReferenceExpression) {
      val clazz = exp.resolve()
      if (clazz is KtClass) {
        return clazz
      }
    }
  }
  return null
}

fun KtAnnotated.getAnnotationPropertyValue(annoClass: String, propertyName: String): KtExpression? {
  val anno = this.getAnnotation(annoClass)
  return anno?.getAttributeValue(propertyName)
}

fun KtAnnotationEntry.getAttributeValue(propertyName: String): KtExpression? {
  this.valueArguments.forEach {
    val n = it.getArgumentName()
    if ((propertyName == "value" && !it.isNamed())
        || (n is KtValueArgumentName && n.text == propertyName)
    ) {
      return it.getArgumentExpression()
    }
  }
  return null
}


/**
 *
 * @author 吴昊
 * @date 2024/1/18 9:53
 * @since v0.0.0
 * @version 1.0
 */
class KtMethodWrapper(
    fn: KtNamedFunction,
    private val classWrapper: KtClassWrapper,
    private val tableInfo: XmllessTableInfo
) : BasePsiMethodWrapper<KtNamedFunction>(fn) {

  companion object {
    val METHOD_ANNO: String = XmllessMethod::class.java.name
  }

  private fun getRealReturnType(): PsiElement? {
    val typeClass = fn.getReturnTypeReference()?.typeElement?.actualType?.resolveKtClass() ?: return null
    val interfaces = typeClass.getAllSuperClassNames()
    val hasGenericType = interfaces.containsAny(
        listOf(
            ClassNames.JAVA_COLLECTION,
            ClassNames.KOTLIN_COLLECTION,
            ClassNames.SPRING_DATA_PAGE,
            ClassNames.MYBATIS_PLUS_PAGE
        )
    )
    return if (hasGenericType) {
      val fullType = fn.getReturnTypeReference()?.typeElement?.actualType!!
      val firstType = fullType.typeArgumentsAsTypes[0]
      firstType.typeElement?.actualType?.resolveKtClass()
    } else {
      typeClass
    }
  }

  override fun resolveReturnMappingList(): FieldMappingList? {
    val ktClass = getRealReturnType() ?: return null
    if (ktClass is KtClass) {
      val wrapper = KtClassWrapper(ktClass)
      return if (ktClass != classWrapper.classElement
          && !fn.hasAnnotation(JsonResult::class.java)
          && wrapper.isComplex()
      ) {
        val currentTableInfo = TableInfoProvider.getOrInitXmllessTableInfo(wrapper)
        MappingResolver.resolveNonEntityClass(
            wrapper, wrapper.getClassName(), tableInfo, currentTableInfo
        )
      } else {
        null
      }
    }
    return null
  }

  override fun getReturnType(): Class<*>? {
    return null
  }

  override fun getParameters(): List<ParameterInfo> {
    return resolveParameters(fn)
  }

  override fun getXmllessMethod(): BaseXmllessMethodWrapper {
    return KtXmllessMethodWrapper(fn)
  }

  override fun getPropertiesMappingInner(): Map<String, String> {
    val ktClass = getRealReturnType() ?: return mapOf()
    if (ktClass is KtClass) {
      return ktClass.getAllFields().filter {
        it.hasAnnotation(PropertyMapping::class.java.name)
      }.associate {
        when (it) {
          is KtProperty   -> it.name!!
          is KtParameter  -> it.name!!
          is PsiField     -> it.name
          is PsiParameter -> it.name
          else            -> error("无法识别的字段类型${it}")
        } to it.getStringAnnoValue(PropertyMapping::class.java.name, "value")!!
      }
    } else if (ktClass is PsiClass) {
      return ktClass.allFields.filter {
        it.hasAnnotation(PropertyMapping::class.java.name)
      }.associate {
        it.name to it.getStringAnnoValue(PropertyMapping::class.java, PropertyMapping::value)!!
      }
    }
    return mapOf()
  }

  override fun getFullName(): String {
    return fn.containingClass()?.fqName?.toString() + "." + fn.name
  }

  override fun getName(): String {
    return fn.name ?: error("method name is null")
  }

  override fun getOptionalParameterNames(): Array<String> {
    return fn.valueParameters.map {
      it.getStringAnnoValue(Param::class.java, Param::value) ?: it.name ?: "UNKNOWN"
    }.toTypedArray()
  }

  override fun getModelFields(): List<AnnotatedElementDetailInfo> {
    return classWrapper.classElement.getAllFields().map {
      it.toAnnotationElementWrapper()
    }.map {
      AnnotatedElementDetailInfo(it)
    }
  }

  override fun getModelName(): String {
    return classWrapper.classElement.fqName?.toString() ?: error("model name is null")
  }

  private fun resolveParameters(fn: KtNamedFunction): List<ParameterInfo> {
    return fn.valueParameters.map {
      val detail = ElementInfoBuilder.build(KtParameterWrapper(it))
      ParameterInfo(
          detail,
          it.name ?: "",
          detail.typeInfo,
          detail.chooseCriteria
      )
    }
  }

}
